﻿using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.IO.IsolatedStorage;
using System.Linq;
using System.Text;
using System.Reflection;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using System.Windows.Threading;
using System.Xml;
using System.Xml.Linq;
using System.Xml.XPath;
using System.Text.RegularExpressions;

namespace Netfraction
{
	/// <summary>
	/// Interaction logic for MainWindow.xaml
	/// </summary>
	public partial class MainWindow : Window
	{
		public enum UserUpdateAction
		{
			Online,
			Offline,
			Changed
		}

		private delegate void UpdatePublicChatDelegate(Netfraction.Identity user, string text);
		private delegate void UpdateUsersDelegate(Netfraction.Identity identity, UserUpdateAction action);
		private delegate void ShowFileListDelegate(System.Xml.Linq.XDocument xDoc, Netfraction.Identity remoteIdentity);
		private delegate void UpdateChatDocDelegate();
		private delegate void LaunchChatWindowDelegate(Netfraction.ClientInterface.ChatEventArgs args);
		private delegate void UpdateWindowTitleDelegate(string title);

		public static Netfraction.Identity localIdentity;

		private FlowDocument chatDoc;
		private ChatWindowManager chatWindowManager;

		private System.Windows.Forms.NotifyIcon notifyIcon;

		public static Dictionary<string, string> emoticonList;

		public static Netfraction.LocalMachine.Client mClient;
		static Netfraction.RemoteMachine.Hub hub;

		#region Get App Path
		/// <summary>
		/// Gets the name of the product.
		/// </summary>
		/// <param name="assembly">The assembly.</param>
		/// <returns></returns>
		public static string GetProductName(Assembly assembly)
		{
			string productName = "";

			if (Attribute.IsDefined(assembly, typeof(AssemblyProductAttribute)))
			{
				AssemblyProductAttribute asProductName = (AssemblyProductAttribute)Attribute.GetCustomAttribute(assembly, typeof(AssemblyProductAttribute));
				productName = asProductName.Product;
			}

			return productName;
		}

		/// <summary>
		/// Gets the assembly company.
		/// </summary>
		/// <param name="assembly">The assembly.</param>
		/// <returns></returns>
		public static string GetAssemblyCompany(Assembly assembly)
		{
			string company = "";

			if (Attribute.IsDefined(assembly, typeof(AssemblyCompanyAttribute)))
			{
				AssemblyCompanyAttribute ascompany = (AssemblyCompanyAttribute)Attribute.GetCustomAttribute(assembly, typeof(AssemblyCompanyAttribute));
				company = ascompany.Company;
			}

			return company;
		}
		#endregion

		private static string ApplicationDataDir;
		public static string EmoticonsDir;

        private bool hasLoaded;

		public MainWindow()
		{
			InitializeComponent();

			SetAppDataPath();
			CreateNotifyIcon();
			
			chatDoc = new FlowDocument();
			chatDoc.SetValue(Paragraph.LineHeightProperty, 1.0);

			TryLoadEmoticons();

			chatWindowManager = new ChatWindowManager();
			userListView.MouseDoubleClick += new MouseButtonEventHandler(userListView_MouseDoubleClick);
			messageTextBox.KeyDown += new KeyEventHandler(messageTextBox_KeyDown);

			Netfraction.ClientInterface.Chat.OnChatMessageReceived += new Netfraction.ClientInterface.ChatMessageEventHandler(Chat_OnChatMessageReceived);
			Netfraction.ClientInterface.Peers.OnPeerOnline += new Netfraction.ClientInterface.PeerChangedEventHandler(Peers_OnPeerOnline);
			Netfraction.ClientInterface.Peers.OnPeerChanged += new Netfraction.ClientInterface.PeerChangedEventHandler(Peers_OnPeerChanged);
			Netfraction.ClientInterface.Peers.OnPeerOffline += new Netfraction.ClientInterface.PeerChangedEventHandler(Peers_OnPeerOffline);
			Netfraction.ClientInterface.Peers.OnHubNameChanged += new Netfraction.ClientInterface.HubNameChangedEventHandler(Peers_OnHubNameChanged);
			Netfraction.LocalMachine.Client.OnClientIdSet += new Netfraction.LocalMachine.ClientIdEventHandler(Client_OnClientIDSet);

            //Netfraction.Network.Protocol.ADCS.Security.Certificate c = new Network.Protocol.ADCS.Security.Certificate();
            //c.GenerateCertificates();

            hasLoaded = false;
            ClientSettings.OnClientSettingsLoaded += (o, e) =>
            {
                if (!hasLoaded)
                {
                    mClient = Netfraction.LocalMachine.Client.GetInstance(ClientSettings.Identity.PrivateId.Length > 0 ? ClientSettings.Identity.PrivateId : string.Empty);

                    this.Background = new SolidColorBrush(ClientSettings.Appearance.Background.Colour);
                    chatTextBox.Background = this.Background;

                    HubAddressStruct hubAddress = ParseHubAddress(ClientSettings.Connection.HubAddress);
                    hub = new Netfraction.RemoteMachine.Hub(hubAddress.ConnectionType == ConnectionType.ADCS ? true : false);
                    hub.Connection.Connect(new System.Net.IPEndPoint(hubAddress.IPAddress, hubAddress.Port));
                }
            };

            if (File.Exists(System.IO.Path.Combine(ApplicationDataDir, "Client.xml")))
                ClientSettings.LoadSettings(System.IO.Path.Combine(ApplicationDataDir, "Client.xml"));
            else
                ShowSettingsDialog();
		}

		private static void SetAppDataPath()
		{
			Assembly curAssembly = System.Reflection.Assembly.GetExecutingAssembly();
			string curCompany = System.IO.Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), GetAssemblyCompany(curAssembly));
			ApplicationDataDir = System.IO.Path.Combine(curCompany, GetProductName(curAssembly));
		}

		private void TryLoadEmoticons()
		{
			emoticonList = new Dictionary<string, string>();
			EmoticonsDir = System.IO.Path.Combine(ApplicationDataDir, "Emoticon Packs" + System.IO.Path.DirectorySeparatorChar + "Default" + System.IO.Path.DirectorySeparatorChar);
			try
			{
				using (FileStream fStream = new FileStream(System.IO.Path.Combine(EmoticonsDir, "Emoticons.xml"), FileMode.Open, FileAccess.Read))
				{
					LoadEmoticons(fStream);
				}
			}
			catch (Exception ex)
			{
				Console.WriteLine(ex.Message);
			}
		}

		private void CreateNotifyIcon()
		{
			notifyIcon = new System.Windows.Forms.NotifyIcon();
			notifyIcon.Icon = new System.Drawing.Icon(@"AppIcon.ico");
			// The ContextMenu property sets the menu that will
			// appear when the systray icon is right clicked.
			//notifyIcon1.ContextMenu = this.contextMenu1;
			notifyIcon.Text = "Netfraction";
			notifyIcon.Visible = true;
			notifyIcon.Click += new System.EventHandler(notifyIcon_Click);
		}

		void Client_OnClientIDSet(Netfraction.LocalMachine.ClientIdEventArgs args)
		{
			Netfraction.Identity ident = new Netfraction.Identity();
			ident.PrivateId = args.PrivateId;
			ident.ClientId = args.ClientId;

			ident.Nickname = ClientSettings.Identity.Name;
			ident.Password = ClientSettings.Identity.Password;
			ident.Description = ClientSettings.Identity.Description;
			ident.Email = ClientSettings.Identity.Email;
			ident.ShareSize = 0;
			ident.SharedFilesCount = 0;
			ident.MaximumDownloadSpeed = 13107200;
			ident.MaximumUploadSpeed = 13107200;
			ident.UploadSlots = 0;
            if (!string.IsNullOrEmpty(ClientSettings.Connection.GatewayIP))
                ident.IPv4Address = System.Net.IPAddress.Parse(ClientSettings.Connection.GatewayIP);

			ident.AwayStatus = Netfraction.IdentityAwayStatus.Online;

			mClient.SetIdentity(ident);
		}

		void Peers_OnHubNameChanged(Netfraction.ClientInterface.HubNameEventArgs args)
		{
			this.Dispatcher.Invoke(DispatcherPriority.Normal, new UpdateWindowTitleDelegate(UpdateWindowTitle), args.HubName);
		}

		void Peers_OnPeerOffline(Netfraction.ClientInterface.PeerEventArgs args)
		{
			try
			{
				this.Dispatcher.Invoke(System.Windows.Threading.DispatcherPriority.Normal,
					new UpdateUsersDelegate(UpdateUsers), args.Identity, new object[] { UserUpdateAction.Offline });
			}
			catch (Exception ex)
			{
				Console.WriteLine(ex.TargetSite.Name);
				Console.WriteLine(ex.Message);
			}
		}

		void Peers_OnPeerChanged(Netfraction.ClientInterface.PeerEventArgs args)
		{
			try
			{
				this.Dispatcher.Invoke(System.Windows.Threading.DispatcherPriority.Normal,
					new UpdateUsersDelegate(UpdateUsers), args.Identity, new object[] { UserUpdateAction.Changed });
			}
			catch (Exception ex)
			{
				Console.WriteLine(ex.TargetSite.Name);
				Console.WriteLine(ex.Message);
			}
		}

		void Peers_OnPeerOnline(Netfraction.ClientInterface.PeerEventArgs args)
		{
			try
			{
				if (args.IsLocalClient)
				{
					localIdentity = args.Identity;
				}
				this.Dispatcher.Invoke(System.Windows.Threading.DispatcherPriority.Normal,
					new UpdateUsersDelegate(UpdateUsers), args.Identity, new object[] { UserUpdateAction.Online });
			}
			catch (Exception ex)
			{
				Console.WriteLine(ex.TargetSite.Name);
				Console.WriteLine(ex.Message);
			}
		}

		void Chat_OnChatMessageReceived(Netfraction.ClientInterface.ChatEventArgs args)
		{
			if ((args.ChatOptions & Netfraction.ClientInterface.ChatOptions.METext) == Netfraction.ClientInterface.ChatOptions.METext)
			{
				args.MessageText = "/me " + args.MessageText;
			}
			if ((args.ChatOptions & Netfraction.ClientInterface.ChatOptions.PublicMessage) == Netfraction.ClientInterface.ChatOptions.PublicMessage |
				(args.ChatOptions & Netfraction.ClientInterface.ChatOptions.HubMessage) == Netfraction.ClientInterface.ChatOptions.HubMessage)
			{
				DisplayMessage(args.Source, args.MessageText);
			}
			else if ((args.ChatOptions & Netfraction.ClientInterface.ChatOptions.PrivateMessage) == Netfraction.ClientInterface.ChatOptions.PrivateMessage)
			{
				this.Dispatcher.Invoke(System.Windows.Threading.DispatcherPriority.Normal,
					new LaunchChatWindowDelegate(LaunchChatWindow), args);
			}
			switch (args.ChatOptions)
			{
				case Netfraction.ClientInterface.ChatOptions.None:
					break;
				case Netfraction.ClientInterface.ChatOptions.METext:
					break;
				case Netfraction.ClientInterface.ChatOptions.PublicMessage:
					break;
				case Netfraction.ClientInterface.ChatOptions.PrivateMessage:
					break;
				case Netfraction.ClientInterface.ChatOptions.GroupPrivateMessage:
					break;
				case Netfraction.ClientInterface.ChatOptions.HubMessage:
					break;
				default:
					break;
			}
		}

		private void notifyIcon_Click(object Sender, EventArgs e)
		{
			// Show the form when the user clicks on the notify icon.
			// Set the WindowState to normal if the form is minimized.

			this.ShowInTaskbar = true;
			this.Show();

			if (this.WindowState == WindowState.Minimized)
				this.WindowState = WindowState.Normal;


			// Activate the form.
			this.Activate();
		}

		private void LoadEmoticons(System.Windows.Resources.StreamResourceInfo streamResourceInfo)
		{
			LoadEmoticons(streamResourceInfo.Stream);
		}

		private void LoadEmoticons(Stream fStream)
		{
			XmlReader xReader = XmlReader.Create(fStream);
			XDocument xDoc = XDocument.Load(xReader);

			foreach (XElement item in xDoc.XPathSelectElements("//Emoticons/Emoticon"))
			{
				emoticonList.Add(item.Attribute("regexp").Value, item.Attribute("href").Value);
			}
		}

		private void UpdateWindowTitle(string title)
		{
			this.Title = title;
		}

		void messageTextBox_KeyDown(object sender, KeyEventArgs e)
		{
			if (e.Key == Key.Return)
			{
				SendMessage(messageTextBox.Document);
				messageTextBox.Document = new FlowDocument();
			}
		}

		private void SendMessage(FlowDocument message)
		{
			TextRange tex = new TextRange(message.ContentStart, message.ContentEnd);
			string text = tex.Text.Trim();
			Netfraction.ClientInterface.Chat.SendPublicMessage(hub, text);
		}

		void userListView_MouseDoubleClick(object sender, MouseButtonEventArgs e)
		{
			try
			{
				DependencyObject dep = (DependencyObject)e.OriginalSource;
				while ((dep != null) && !(dep is ListViewItem))
				{
					dep = VisualTreeHelper.GetParent(dep);
				}
				if (dep == null) return;
				Netfraction.Identity item = (Netfraction.Identity)userListView.ItemContainerGenerator.ItemFromContainer(dep);

				/*DCSharp.Backend.Managers.ListDownloader lDow = dcRuntime.DownloadManager.DownloadList(item);

				lDow.ListReceived += delegate
				{
					this.Dispatcher.Invoke(DispatcherPriority.Normal, new ShowFileListDelegate(ShowFileList), lDow.Result, new object[]{item});
				};*/
			}
			catch (Exception ex)
			{
				Console.WriteLine(ex.Message);
			}
		}

		void ShowFileList(/*DCSharp.Xml.FileList list, */System.Xml.Linq.XDocument xDoc, Netfraction.Identity remoteIdentity)
		{
			FileListWindow fileList = new FileListWindow(xDoc, remoteIdentity);
			fileList.Show();
		}

		private void UpdateUsers(Netfraction.Identity identity, UserUpdateAction action)
		{
			var listItems = from p in mClient.Peers
							orderby p.Identity.IsOperator descending, p.Identity.Nickname
							select p.Identity;
			userListView.ItemsSource = listItems;

			switch (action)
			{
				case UserUpdateAction.Online:
					DisplayMessage(identity, "/join");
					break;
				case UserUpdateAction.Offline:
					DisplayMessage(identity, "/leave");
					break;
			}
		}

		public void LaunchChatWindow(Netfraction.ClientInterface.ChatEventArgs args)
		{
			// the hub echo's PM's back to the client. check if the local client is the original sender or if a remote client was
			if (args.Source.ClientId == args.RespondTo.ClientId)
			{
				chatWindowManager.DisplayMessage(hub, args.Source, args.Target, args.MessageText, true);
			}
			else
			{
				chatWindowManager.DisplayMessage(hub, args.Target, args.Source, args.MessageText, false);
			}
		}

		protected void DisplayMessage(Netfraction.Identity user, string message)
		{
			try
			{
				this.Dispatcher.Invoke(System.Windows.Threading.DispatcherPriority.Normal,
					new UpdatePublicChatDelegate(UpdatePublicChat), user, message);
			}
			catch (Exception e)
			{
				Console.WriteLine(e.TargetSite.Name);
				Console.WriteLine(e.Message);
			}
		}
		public void UpdatePublicChat(Netfraction.Identity user, string text)
		{
			chatDoc.Blocks.Add(ChatDocument.CreateChatDocument(user, text));
			
			this.Dispatcher.Invoke(System.Windows.Threading.DispatcherPriority.Normal,
					new UpdateChatDocDelegate(UpdateChatDoc));
		}

		public void UpdateChatDoc()
		{
			try
			{
				chatTextBox.Document = chatDoc;
				chatTextBox.ScrollToEnd();
			}
			catch (Exception e)
			{
				Console.WriteLine(e.Message);
				throw;
			}
		}

		private void usersContextMenu_ContextMenuOpening(object sender, ContextMenuEventArgs e)
		{
			Netfraction.Identity identity = GetIdentityFromDependencyObject((DependencyObject)e.OriginalSource);
			if (identity == null) return;

			getFileListMenuItem.Tag = identity;
			sendPrivateMessageMenuItem.Tag = identity;
		}

		void SendPrivateMessageMenuItem_Click(object sender, RoutedEventArgs e)
		{
			Netfraction.Identity remoteIdentity = (sender as MenuItem).Tag as Netfraction.Identity;
			if (remoteIdentity != localIdentity)
			{
				chatWindowManager.DisplayMessage(hub, localIdentity, remoteIdentity, null, true);
			}
		}

		void GetFileListMenuItem_Click(object sender, RoutedEventArgs e)
		{
			Netfraction.Identity remoteIdentity = (sender as MenuItem).Tag as Netfraction.Identity;
			Netfraction.Managers.DownloadManager dow = Netfraction.Managers.DownloadManager.GetInstance();
			//this.Dispatcher.Invoke(new Netfraction.Managers.FileListDownloadEventHandler(DownloadManager_OnFileListDownloaded), DispatcherPriority.Normal);
			//Netfraction.Managers.DownloadManager.OnFileListDownloaded += new Netfraction.Managers.FileListDownloadEventHandler(DownloadManager_OnFileListDownloaded);

			Netfraction.Managers.DownloadManager.OnFileListDownloaded += delegate(Netfraction.Managers.FileListDownloadEventArgs args)
			{
				this.Dispatcher.Invoke(DispatcherPriority.Normal, new ShowFileListDelegate(ShowFileList), args.XmlDocument, new object[] { args.Peer.Identity });
			};
			dow.GetFileList(hub, remoteIdentity);
			/*DCSharp.Backend.Managers.ListDownloader lDow = dcRuntime.DownloadManager.DownloadList(remoteIdentity);

			lDow.ListReceived += delegate
			{
				this.Dispatcher.Invoke(DispatcherPriority.Normal, new ShowFileListDelegate(ShowFileList), lDow.Result, new object[]{remoteIdentity});
			};*/
		}

		void DownloadManager_OnFileListDownloaded(Netfraction.Managers.FileListDownloadEventArgs e)
		{
			ShowFileList(e.XmlDocument, e.Peer.Identity);
		}

		private Netfraction.Identity GetIdentityFromDependencyObject(DependencyObject dep)
		{
			while ((dep != null) && !(dep is ListViewItem))
			{
				dep = VisualTreeHelper.GetParent(dep);
			}
			if (dep == null) return null;
			return ((Netfraction.Identity)userListView.ItemContainerGenerator.ItemFromContainer(dep));
		}

		private void SettingMenuItem_Click(object sender, RoutedEventArgs e)
		{
            ShowSettingsDialog();
		}

        private static void ShowSettingsDialog()
        {
            SettingsWindow sw = new SettingsWindow();
            sw.ShowDialog();
        }

		private void Window_Closing(object sender, System.ComponentModel.CancelEventArgs e)
		{
			if (mClient != null)
			{
				Console.WriteLine(mClient.Identity.PrivateId);
				ClientSettings.Identity.PrivateId = mClient.Identity.PrivateId;
				ClientSettings.SaveSettings();
			}

			// Get rid of the tray icon
			notifyIcon.Dispose();


			//dcRuntime.Shutdown();
		}

		private void Window_StateChanged(object sender, EventArgs e)
		{
			if (WindowState.Minimized == this.WindowState)
			{
				this.ShowInTaskbar = false;
				//notifyIcon.ShowBalloonTip(500);
				this.Hide();
			}
		}

		public enum ConnectionType
		{
			ADC,
			ADCS
		}

		public class HubAddressStruct
		{
			public ConnectionType ConnectionType;
			public string DNSAddress;
			public System.Net.IPAddress IPAddress;
			public int Port;
		}

		public static HubAddressStruct ParseHubAddress(string address)
		{
			string adcRegex = "adc://(?<host>.+):(?<port>[0-9]+)/?(?<extra>.?)";
			string adcsRegex = "adcs://(?<host>.+):(?<port>[0-9]+)/?(?<extra>.?)";

			HubAddressStruct hubAddress = new HubAddressStruct();

			Match m = Regex.Match(address, adcRegex);
			if (m.Success)
			{
				hubAddress.ConnectionType = ConnectionType.ADC;
			}
			else
			{
				m = Regex.Match(address, adcsRegex);
				if (m.Success)
					hubAddress.ConnectionType = ConnectionType.ADCS;
			}

			if (!m.Success)
				return null;

			if (!System.Net.IPAddress.TryParse(m.Groups["host"].Value, out hubAddress.IPAddress))
			{
				hubAddress.IPAddress = ResolveDnsAddress(m.Groups["host"].Value);
				hubAddress.DNSAddress = m.Groups["host"].Value;
			}
			hubAddress.Port = int.Parse(m.Groups["port"].Value);

			return hubAddress;
		}

		public static System.Net.IPAddress ResolveDnsAddress(string address)
		{
			System.Net.IPHostEntry iphe = System.Net.Dns.GetHostEntry(address);
			if (iphe.AddressList.Length > 0)
			{
				return iphe.AddressList[0];
			}

			return System.Net.IPAddress.None;
		}
	}

	public class UserToStatusImageConverter : IValueConverter
	{
		public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
		{
			Uri uri = null;
			try
			{
				Netfraction.Identity identity = value as Netfraction.Identity;
				if (identity.IsOperator)
				{
					uri = new Uri("pack://application:,,,/Images/Contact_Operator.png");
				}
				else
				{
					uri = new Uri("pack://application:,,,/Images/Contact_Online.png");
					/*switch (identity.Flags)
					{
						case DCSharp.Identity.FlagValues.Normal:
							uri = new Uri("pack://application:,,,/Images/Contact_Online.png");
							break;
						case DCSharp.Identity.FlagValues.Away | DCSharp.Identity.FlagValues.Away2:
							uri = new Uri("pack://application:,,,/Images/Contact_Away.png");
							break;
						case DCSharp.Identity.FlagValues.Server | DCSharp.Identity.FlagValues.Server2:
							uri = new Uri("pack://application:,,,/Images/Contact_Server.png");
							break;
						case DCSharp.Identity.FlagValues.ServerAway | DCSharp.Identity.FlagValues.ServerAway2:
							uri = new Uri("pack://application:,,,/Images/Contact_ServerAway.png");
							break;
						case DCSharp.Identity.FlagValues.Fireball | DCSharp.Identity.FlagValues.Fireball2:
							uri = new Uri("pack://application:,,,/Images/Contact_Fireball.png");
							break;
						case DCSharp.Identity.FlagValues.FireballAway | DCSharp.Identity.FlagValues.FireballAway2:
							uri = new Uri("pack://application:,,,/Images/Contact_FireballAway.png");
							break;
						default:
							uri = new Uri("pack://application:,,,/Images/Contact_Online.png");
							break;
					}*/
				}
			}
			catch (Exception e)
			{
				Console.WriteLine(e.Message);
			}
			if (uri != null)
			{
				return new BitmapImage(uri);
			}
			else
			{
				return null;
			}
		}

		public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
		{
			throw new NotImplementedException();
		}
	}
}
